#include <autoassignprogrambinding.h>
#include <enums.h>
#include <gfx/image.h>
#include <gfx/ieffect.h>
#include <gfx/ieffectprogram.h>
#include <gfx/iprogram.h>
#include <gfx/itexture.h>
#include <gfx/material.h>
#include <materialmanager.h>
#include <manager/materialmanager.h>
#include <session.h>
#include <ldr/loadingsystem.h>
#include <vfs.h>


/* ***********************************************
 *   BaseMaterialItem
 * ***********************************************/

MaterialData::MaterialData(const QString& name)
  : QObject ()
  , _name (name)
{
  connect (&_groupItem, SIGNAL(Changed()), this, SIGNAL(Changed()));
}

MaterialData::MaterialData(const MaterialData &other)
  : QObject()
  , _name(other._name)
  , _extension (other._extension)
  , _groupItem (other._groupItem)
{
}

void MaterialData::SetName(const QString &name)
{
  if (_name != name)
    {
      _name = name;
      emit NameChanged();
    }
}

const QString& MaterialData::GetName() const
{
  return _name;
}

void MaterialData::SetExtensionName(const QString &name)
{
  if (_extension != name)
    {
      _extension = name;
      emit NameChanged();
    }
}

const QString& MaterialData::GetExtensionName() const
{
  return _extension;
}

void MaterialData::SetExportName(const QString &name)
{
  QStringList names = name.split(".");
  if (names.size() == 1)
    {
      _name = names[0];
    }
  else if (names.size() != 0)
    {
      QString ext = names.back();
      names.pop_back();
      SetExtensionName("." + ext);

      QString aname = names.join(".");
      SetName(aname);
    }

  emit NameChanged();
}


QString MaterialData::GetExportName() const
{
  return _name + _extension;
}

QString MaterialData::GetFullname() const
{
  return _name + _extension;
}


void MaterialData::SetCategory (const Category& item)
{
  _groupItem = item;
}

Category& MaterialData::GetCategory()
{
  return _groupItem;
}

const Category& MaterialData::GetCategory() const
{
  return _groupItem;
}


void MaterialData::Change ()
{
  emit Changed ();
}

void MaterialData::Save()
{
  emit Changed();
}

void MaterialData::Move(const Category& group)
{
  _groupItem = group;
}

void MaterialData::Remove()
{
  emit Removed();
}

/* ***********************************************
 *   Texture
 * ***********************************************/

unsigned Texture::static_idx = 0;

Texture::Texture (MaterialManager* manager, iTexture* texture)
  : MaterialData ("Texture")
  , _idx (++Texture::static_idx)
  , _filename ("")
  , _wrap (0)
  , _filter (0)
  , _anisotropy (0)
  , _texture (0)
  , _materialManager (manager)
{
  SetExtensionName(".tex2D");
  CE_SET(_texture, texture);
}

Texture::Texture(const Texture &other)
  : MaterialData(other.GetName())
  , _idx (other._idx)
  , _filename (other._filename)
  , _wrap (other._wrap)
  , _filter (other._filter)
  , _anisotropy (other._anisotropy)
  , _texture (0)
  , _materialManager(other._materialManager)
{
  CE_SET(_texture, other._texture);
}

Texture::~Texture ()
{
  CE_UNSET(_texture);
}


unsigned Texture::GetIdx() const
{
  return _idx;
}


iTexture* Texture::GetTexture()
{
  return _texture;
}


const iTexture* Texture::GetTexture() const
{
  return _texture;
}

void Texture::SetFilename(const QString &filename)
{
  _filename = filename;
}

const QString& Texture::GetFilename() const
{
  return _filename;
}

void Texture::SetWrap(int wrap)
{
  _wrap = wrap;
}

int Texture::GetWrap () const
{
  return _wrap;
}

void Texture::SetFilter(int filter)
{
  _filter = filter;
}

int Texture::GetFilter () const
{
  return _filter;
}

void Texture::SetAnisotropy(int anisotrophy)
{
  _anisotropy = anisotrophy;
}

int Texture::GetAnisotropy() const
{
  return _anisotropy;
}

void Texture::SetMipMap (int mipMap)
{
	_mipMap = mipMap;
}

int Texture::GetMipMap () const
{
	return _mipMap;
}

void Texture::UpdateData()
{
  if (!_texture || _texture->GetType() != TT_Texture2D)
    {
      return;
    }
  iDevice* device = Session::Get()->GetDevice();

  ceTextureDescriptor desc;
  // setup the filter
  switch (_filter)
    {
    case Texture::Filter_Default:
      desc.filter = device->GetDefaultFilter();
      break;
    case Texture::Filter_Anisotropic:
      desc.filter = F_Anisotropic;
      break;
    case Texture::Filter_Trilinear:
      desc.filter = F_Trilinear;
      break;
    case Texture::Filter_Bilinear:
      desc.filter = F_Bilinear;
      break;
    case Texture::Filter_Nearest:
      desc.filter = F_Nearest;
      break;
    }

  // setup the wrap mode
  switch (_wrap)
    {
    case Texture::Wrap_Default:
      desc.wrapMode = device->GetDefaultWrapMode();
      break;
    case Texture::Wrap_Clamp:
      desc.wrapMode = WM_Clamp;
      break;
    case Texture::Wrap_ClampToBorder:
      desc.wrapMode = WM_ClampToBorder;
      break;
    case Texture::Wrap_ClampToEdge:
      desc.wrapMode = WM_ClampToEdge;
      break;
    case Texture::Wrap_Repeat:
      desc.wrapMode = WM_Repeat;
      break;
    }

  switch (_anisotropy)
    {
    case 0:
      desc.anisotropic = device->GetDefaultAnisotropic();
      break;
    default:
      desc.anisotropic = _anisotropy;
    }

  switch (_mipMap)
    {
    case Texture::MipMap_Default:
      desc.mipMap = device->IsDefaultMipMap();
      break;
    case Texture::MipMap_On:
      desc.mipMap = true;
      break;
    case Texture::MipMap_Off:
      desc.mipMap = false;
    }

  iFile* file = ceVFS::Get()->Open(_filename.toStdString());
  ceImage* img = ceLoadingSystem::Get()->LoadImage("", file);
  desc.size = ceRect (img->GetWidth(), img->GetHeight());
  desc.format = img->GetFormat();
  img->SetCurrentLayer(0);

  iTexture2D* txt2d = static_cast<iTexture2D*>(_texture);
  txt2d->Convert(desc);
  txt2d->SetData(img->GetFormat(), img->GetData());

  img->Release();
}

void Texture::Save()
{
  UpdateData();
  _materialManager->SaveTexture(this);
  MaterialData::Save();
}

void Texture::Move (const Category& group)
{
  MaterialData::Move(group);
  _materialManager->MoveTexture(this);
}


void Texture::Remove()
{
  _materialManager->RemoveTexture(this);
  MaterialData::Remove ();
}

/* ***********************************************
 *   Shader
 * ***********************************************/


Shader::Shader(MaterialManager* MaterialManager, iShader* shader)
  : MaterialData ("Shader")
  , _code ("")
  , _materialManager(MaterialManager)
  , _shader(shader)
  , _version (GLSL_110)
{
  SetExtensionName(GetExtensionName());
}


QString Shader::GetExtensionName() const
{
  if (!_shader)
    {
      return ".undefined";
    }
  switch (_shader->GetType())
    {
  case ST_Vertex:
    return ".vert";
  case ST_Fragment:
    return ".frag";
    }
  // just keep the compiler calm
  return ".undefined";
}

QString Shader::GetFullname() const
{
  return GetName() + GetExtensionName() + " (" + GetShaderVersionName(_version) + ")";
}

void Shader::SetCode(const QString &code)
{
  _code = code;
  ScanText();
  _shader->SetSource(code.toStdString());
}

const QString& Shader::GetCode() const
{
  return _code;
}

void Shader::SetShaderType(ceShaderType type)
{
  if (type != _shader->GetType())
    {
      _shader->ChangeType(type);
      SetExtensionName(GetExtensionName());
    }
}

ceShaderType Shader::GetShaderType() const
{
  return _shader->GetType();
}


Shader::Version Shader::GetVersion() const
{
  return _version;
}

iShader* Shader::GetShader()
{
  return _shader;
}

void Shader::Save()
{
  ScanText();
  if (!_shader->Compile())
    {
      printf ("Unable to compile shader '%s'\n",
              _shader->GetCompileInfoLog().c_str());
      fflush(stdout);
    }
  _materialManager->SaveShader(this);
  MaterialData::Save();
}

void Shader::Move (const Category& group)
{
  MaterialData::Move(group);
  _materialManager->MoveShader(this);
}


void Shader::Remove()
{
  _materialManager->RemoveShader(this);
  MaterialData::Remove ();
}

Shader::Version Shader::GetShaderVersionFromString(const QString &name)
{
  if (name == "110") return GLSL_110;
  else if (name == "120") return GLSL_120;
  else if (name == "130") return GLSL_130;
  else if (name == "140") return GLSL_140;
  else if (name == "150") return GLSL_150;
  else if (name == "330") return GLSL_330;
  else if (name == "400") return GLSL_400;
  else if (name == "410") return GLSL_410;
  else if (name == "420") return GLSL_420;
  return GLSL_110;
}


QString Shader::GetShaderVersionString (Shader::Version version)
{
  switch (version)
    {
    case GLSL_110: return "110";
    case GLSL_120: return "120";
    case GLSL_130: return "130";
    case GLSL_140: return "140";
    case GLSL_150: return "150";
    case GLSL_330: return "330";
    case GLSL_400: return "400";
    case GLSL_410: return "410";
    case GLSL_420: return "420";
    default:
      break;
    }
  return "";
}
QString Shader::GetShaderVersionName (Shader::Version version)
{
  switch (version)
    {
    case GLSL_110: return "GLSL v1.10";
    case GLSL_120: return "GLSL v1.20";
    case GLSL_130: return "GLSL v1.30";
    case GLSL_140: return "GLSL v1.40";
    case GLSL_150: return "GLSL v1.50";
    case GLSL_330: return "GLSL v3.30";
    case GLSL_400: return "GLSL v4.00";
    case GLSL_410: return "GLSL v4.10";
    case GLSL_420: return "GLSL v4.20";
    default:
      break;
    }
  return "";
}


void Shader::ScanText()
{
  _attributes.clear();
  _uniforms.clear ();
  ScanText (_attributes,	"attribute");
  ScanText (_attributes,  "in");
  ScanText (_attributes,	"inout");
  ScanText (_uniforms,		"uniform");

  EvalVersion ();
}


void Shader::ScanText(QList<QString> &list, const QString &keyword)
{
  QString pattern = "\\b" + keyword +"\\b";
  QString semicolonPattern = ";";

  QRegExp expression(pattern);
  QRegExp semicolonExpression(semicolonPattern);
  int index = _code.indexOf(expression);
  while (index >= 0)
    {
      int length = expression.matchedLength();
      int end = index + length;

      int semicolonIndex = _code.indexOf(semicolonExpression, end);
      if (semicolonIndex >= end)
        {
          int count = semicolonIndex - end;
          QString value = _code.mid(end, count);
          QStringList parts = value.split(QRegExp("\\s+"), QString::SkipEmptyParts);
          if (parts.size() == 2)
            {
              QString variableName = parts[1];
              list.append(variableName);
            }
        }
      index = _code.indexOf(expression, index + length);
    }

}

void Shader::EvalVersion()
{
   QString pattern = "#version\\s*(\\d{3})\\s*";
   QRegExp expression(pattern);
   int index = _code.indexOf(expression);
   if (index >= 0)
     {
        QStringList maches = expression.capturedTexts();
        if (maches.size() == 2)
          {
            Version version = Shader::GetShaderVersionFromString(maches[1]);
            _version = version;
            emit Changed ();
            return;
          }
     }
   _version = GLSL_110;
}

/* ***********************************************
 *   Program
 * ***********************************************/


Program::ShaderProgram::ShaderProgram (iProgram* program)
  : _program(program)
  , _name("Program")
{

}

void Program::ShaderProgram::SetName(const QString &name)
{
  _name = name;
}

const QString& Program::ShaderProgram::GetName() const
{
  return _name;
}

void Program::ShaderProgram::AddShader(Shader *shader)
{
  if (_shaders.contains(shader))
    {
      return;
    }
  _shaders.append(shader);
  _program->AttachShader(shader->GetShader());
}

void Program::ShaderProgram::RemoveShader(Shader *shader)
{
  _shaders.removeAll(shader);
  _program->DetachShader(shader->GetShader());
}

bool Program::ShaderProgram::Relink(Shader *shader)
{
  if (!shader || _shaders.contains(shader))
    {
      Session::Get()->MakeGLContextCurrent();
      foreach (Shader* shd, _shaders)
        {
          if (!shd->GetShader()->IsCompiled())
            {
              return false;
            }
        }
      _program->ClearShaders();
      foreach (Shader* shd, _shaders)
        {
          _program->AttachShader(shd->GetShader());
        }
      if (!_program->Link())
        {
          printf ("Unable to link Shader '%s'\n",
                  _program->GetLinkInfoLog().c_str());
          fflush(stdout);
        }
      return true;
    }
  return false;
}

int Program::ShaderProgram::GetNumberOfShaders() const
{
  return _shaders.size();
}

Shader* Program::ShaderProgram::GetShader(int idx)
{
  if (idx < 0 || idx >= _shaders.size())
    {
      return 0;
    }

  return _shaders[idx];
}

iProgram* Program::ShaderProgram::GetProgram() const
{
  return _program;
}

Shader::Version Program::ShaderProgram::GetVersion() const
{
  Shader::Version v = Shader::GLSL_110;
  foreach (Shader* shader, _shaders)
    {
      Shader::Version sv = shader->GetVersion();
      if (sv > v)
        {
          v = sv;
        }
    }

  return v;
}

Program::Program(MaterialManager* manager)
  : MaterialData ("Program")
  , _materialManager(manager)
{
  SetExtensionName(".prog");
}

Program::Program(const Program &other)
  : MaterialData(other.GetName())
  , _materialManager (other._materialManager)
{
}

Program::ShaderProgram* Program::CreateProgram()
{
  return new ShaderProgram(Session::Get()->GetDevice()->CreateProgram());
}

int Program::AddProgram(ShaderProgram *program)
{
  if (!_programs.contains(program))
    {
      _programs.append(program);
    }
  return _programs.indexOf(program);
}

void Program::RemoveProgram(ShaderProgram *program)
{
  _programs.removeAll(program);
}

int Program::GetNumberOfPrograms() const
{
  return _programs.size ();
}

Program::ShaderProgram* Program::GetProgram(int idx)
{
  if (idx < 0 || idx >= _programs.size())
    {
      return 0;
    }

  return _programs[idx];
}

Program::ShaderProgram* Program::EvalBestProgram()
{
  // TODO: The evaluation of the best matching program must be
  //       based on the current present hardware
  return GetProgram(0);

}

void Program::UpdateBindingValues()
{
  // collet all current bindings from the attached shaders
  QMap<QString, QString> newBindings;
  foreach (ShaderProgram *sp, _programs)
    {
      foreach (Shader* shader, sp->_shaders)
        {

          foreach (QString binding, shader->GetAttributes())
            {
              newBindings[binding] = "";
            }
          foreach (QString binding, shader->GetUniforms())
            {
              newBindings[binding] = "";
            }
        }
    }

  // now check out wich of the bindings we already now and
  // transfer them
  QMap<QString, QString>::iterator it;
  for (it = newBindings.begin(); it != newBindings.end(); ++it)
    {
      if (_bindingMapping.contains(it.key()))
        {
          newBindings[it.key()] = _bindingMapping[it.key()];
        }
    }
  // and set the new bindings
  _bindingMapping = newBindings;
}

void Program::AutoAssignBindings()
{
  QMap<QString, QString>::iterator it;
  for (it = _bindingMapping.begin(); it != _bindingMapping.end(); ++it)
    {
      QString attribute = it.key();
      QString value = it.value();

      if (value.length() > 0)
        {
          // don't overwrite
          continue;
        }

      _bindingMapping[attribute] = AutoAssignProgramBinding::GetBinding (attribute);
    }
}

int Program::GetNumberOfBindings() const
{
  return _bindingMapping.size();
}

const QString& Program::GetBindingKey(int idx) const
{
  QMap<QString, QString>::const_iterator it;
  it = _bindingMapping.begin();
  it += idx;
  return it.key();
}


const QString& Program::GetBindingValue(int idx) const
{
  QMap<QString, QString>::const_iterator it;
  it = _bindingMapping.begin();
  it += idx;
  return it.value();
}

void Program::SetBindingValue(const QString& key, const QString& value)
{
  _bindingMapping[key] = value;
}

void Program::SetBindingValue(int idx, const QString &value)
{
  _bindingMapping[GetBindingKey(idx)] = value;
}


void Program::UpdateProgramBindings()
{
  foreach (ShaderProgram *shdProg, _programs)
    {
      iProgram* prog = shdProg->GetProgram();
      QMap<QString, QString>::iterator it;
      for (it = _bindingMapping.begin(); it != _bindingMapping.end(); ++it)
        {
          QString name = it.key();
          QString binding = it.value();

          ceProgramParameterName ppn (binding.toStdString());
          prog->RegisterSemantic(ppn, name.toStdString());
        }
    }
}

void Program::Relink(Shader* shader)
{
  bool changed = false;
  foreach (ShaderProgram *shdProg, _programs)
    {
      if (shdProg->Relink(shader))
        {
          changed = true;
        }
    }

  if (changed)
    {
      UpdateProgramBindings ();
    }
}

void Program::Save()
{
  _materialManager->SaveProgram(this);
  MaterialData::Save();
}

void Program::Move (const Category& group)
{
  MaterialData::Move(group);
  _materialManager->MoveProgram(this);
}


void Program::Remove()
{
  _materialManager->RemoveProgram(this);
  MaterialData::Remove ();
}

Effect::Effect(MaterialManager *manager, iEffect* effect)
  : MaterialData ("Effect")
  , _materialManager(manager)
	, _effect (effect)
{
  _stages.clear();
  _pass.clear();
  _pass["Diffuse"] = RP_Diffuse;
  _pass["Depth"] = RP_Depth;
  _pass["Shadow"] = RP_Shadow;
	AddStage ();

  SetExtensionName(".fx");
}

void Effect::SetSolver (const QString& solver)
{
	_solverName = solver;
}

const QString& Effect::GetSolver () const
{
	return _solverName;
}

void Effect::ClearStages ()
{
	_effect->ClearEffectPrograms ();
  foreach (Stage stage, _stages)
    {
      CE_UNSET(stage.effect);
      stage.programs.clear();
    }
	_stages.clear ();
}

void Effect::AddStage()
{
  Stage stage;
  stage.programs["Diffuse"] = 0;
  stage.programs["Depth"] = 0;
  stage.programs["Shadow"] = 0;
  stage.effect = Session::Get()->GetDevice()->CreateEffectProgram ();
	stage.effect->AddRef ();

  _stages.append(stage);
	_effect->AddEffectProgram (stage.effect);
}

bool Effect::RemoveStage(unsigned idx)
{
  if (idx >= _stages.size()) return false;
	iEffectProgram* program = _stages[idx].effect;
  _stages.remove(idx);
	_effect->RemoveEffectProgram (program);
  return true;
}

unsigned Effect::GetNumberOfStages() const
{
  return _stages.size();
}

iEffect* Effect::GetEffect ()
{
	return _effect;
}

const iEffect* Effect::GetEffect () const
{
	return _effect;
}


iEffectProgram* Effect::GetEffect (unsigned stage)
{
  assert (stage < _stages.size());
  return _stages[stage].effect;
}

const iEffectProgram* Effect::GetEffect (unsigned stage) const
{
  assert (stage < _stages.size());
  return _stages[stage].effect;
}

void Effect::SetProgram (unsigned stage, const QString& name, Program* program)
{
  assert (stage < _stages.size());

  Stage& stg = _stages[stage];
  stg.programs[name] = program;

  iProgram* prog = 0;
  if (program)
    {
      Program::ShaderProgram* shdProg = program->EvalBestProgram();
      if (shdProg)
        {
          prog = shdProg->GetProgram();
        }
    }

  stg.effect->SetProgram(_pass[name], prog);
}

Program* Effect::GetProgram (unsigned stage, const QString& name)
{
  assert (stage < _stages.size());

  if (_stages[stage].programs.contains(name))
    {
      return _stages[stage].programs[name];
    }
  return 0;
}

unsigned Effect::GetNumberOfPrograms(unsigned stage) const
{
  assert (stage < _stages.size());

  return _stages[stage].programs.size();
}

const QString& Effect::GetProgramName(unsigned stage, unsigned idx)
{
  assert (stage < _stages.size());
  assert (idx < _stages[stage].programs.size());

  QMap<QString, Program*>::iterator it = _stages[stage].programs.begin();
  it += idx;
  return it.key();
}


void Effect::Save()
{
  _materialManager->SaveEffect(this);
  MaterialData::Save();
}

void Effect::Move (const Category& group)
{
  MaterialData::Move(group);
  _materialManager->MoveEffect(this);
}


void Effect::Remove()
{
  _materialManager->RemoveEffect(this);
  MaterialData::Remove ();
}


/* ***********************************************
 *   Program
 * ***********************************************/


Material::Material(MaterialManager *materialManager, ceMaterial* material)
  : MaterialData("Material")
  , _materialManager(materialManager)
  , _material(material)
  , _effect(0)
{
  SetExtensionName(".mat");
}

ceMaterial* Material::GetMaterial()
{
  return _material;
}

void Material::SetEffect(Effect *effect)
{
  _effect = effect;
	_material->SetEffect (effect->GetEffect ());
}

Effect* Material::GetEffect()
{
  return _effect;
}

const Effect* Material::GetEffect() const
{
  return _effect;
}

void Material::Save()
{
  _materialManager->SaveMaterial(this);
  MaterialData::Save();
}

void Material::Move (const Category& group)
{
  MaterialData::Move(group);
  _materialManager->MoveMaterial(this);
}


void Material::Remove()
{
  _materialManager->RemoveMaterial(this);
  MaterialData::Remove ();
}


/* ***********************************************
 *   MaterialManager
 * ***********************************************/

MaterialManager::MaterialManager()
{
  _datas["Textures"] = QList<MaterialData*>();
  _datas["Shaders"] = QList<MaterialData*>();
  _datas["Programs"] = QList<MaterialData*>();
  _datas["Effects"] = QList<MaterialData*>();
  _datas["Materials"] = QList<MaterialData*>();
}

void MaterialManager::RenameMaterialDataGroup (const Category& from, const Category& to)
{
  if (!_datas.contains(from.GetBaseName()))
    {
      return;
    }

  QList<MaterialData*>& datas = _datas[from.GetBaseName()];

  foreach (MaterialData* data, datas)
    {
      if (data->GetCategory().Rename(from, to))
        {
          emit MaterialDataChanged(data);
        }

    }

}


void MaterialManager::RemoveMaterialDataGroup (const Category& group)
{
  if (!_datas.contains(group.GetBaseName()))
    {
      return;
    }

  QList<MaterialData*>& datas = _datas[group.GetBaseName()];

  foreach (MaterialData* data, datas)
    {
      if (data->GetCategory().Remove(group))
        {
          emit MaterialDataChanged(data);
        }
    }
}

/*
 * Texture handling
 */

QList<MaterialData*>& MaterialManager::Textures()
{
  return _datas["Textures"];
}


const QList<MaterialData*>& MaterialManager::Textures() const
{
  QMap<QString, QList<MaterialData*> >::const_iterator it;
  it = _datas.find("Textures");
  assert (it != _datas.end());
  return it.value ();
}
bool MaterialManager::ContainsTexture(Texture *texture)
{
  return Textures ().contains(texture);
}

bool MaterialManager::AddTexture(Texture *texture)
{
  if (ContainsTexture(texture))
    {
      return false;
    }
  Textures ().push_back(texture);

  emit TextureAdded(texture);
  return true;
}

bool MaterialManager::RemoveTexture(Texture *texture)
{
  int idx = Textures ().indexOf(texture);
  if (idx < 0)
    {
      return false;
    }

  RemoveTexture(idx);

  return true;
}

void MaterialManager::MoveTexture(Texture *texture)
{
  assert (false);
}


bool MaterialManager::SaveTexture(Texture *texture)
{
  int idx = Textures ().indexOf(texture);
  if (idx < 0)
    {
      return AddTexture(texture);
    }


  emit MaterialDataChanged(texture);
  return true;

}

unsigned MaterialManager::GetNumberOfTextures() const
{
  return Textures ().size();
}

Texture* MaterialManager::GetTexture(unsigned idx)
{
  return static_cast<Texture*>(Textures ()[idx]);
}

const Texture* MaterialManager::GetTexture(unsigned idx) const
{
  return static_cast<const Texture*>(Textures ()[idx]);
}

void MaterialManager::RemoveTexture(unsigned idx)
{
  Texture* texture = GetTexture (idx);
  emit MaterialDataRemoved(texture);
  Textures ().removeAll(texture);
}

Texture* MaterialManager::CreateTexture()
{
  ceTextureDescriptor desc;
  desc.format       = PF_R8G8B8A8;
  desc.size         = ceRect (1, 1);
  desc.mipMap       = false;
  desc.anisotropic  = 1;


  return new Texture (this, Session::Get()->GetDevice()->CreateTexture2D(desc));
}

/*
 * Shader handling
 */

QList<MaterialData*>& MaterialManager::Shaders()
{
  return _datas["Shaders"];
}

const QList<MaterialData*>& MaterialManager::Shaders() const
{
  QMap<QString, QList<MaterialData*> >::const_iterator it;
  it = _datas.find("Shaders");
  assert (it != _datas.end());
  return it.value ();
}


bool MaterialManager::ContainsShader(Shader *shader)
{
  return Shaders().contains(shader);
}

bool MaterialManager::AddShader(Shader *shader)
{
  if (ContainsShader(shader))
    {
      return false;
    }
  Shaders().push_back(shader);

  emit ShaderAdded(shader);
  return true;
}

bool MaterialManager::RemoveShader(Shader *shader)
{
  int idx = Shaders().indexOf(shader);
  if (idx < 0)
    {
      return false;
    }

  RemoveShader(idx);

  return true;
}

void MaterialManager::MoveShader(Shader *shader)
{
  assert (false);
}

void MaterialManager::UpdatePrograms(Shader* shader)
{
  Session::Get()->MakeGLContextCurrent();
  QList<MaterialData*>& programs = Programs();
  foreach (MaterialData* md, programs)
    {
      Program* program = static_cast<Program*>(md);
      program->Relink(shader);
    }
}

bool MaterialManager::SaveShader(Shader *shader)
{

  int idx = Shaders().indexOf(shader);
  if (idx < 0)
    {
      return AddShader(shader);
    }

  UpdatePrograms (shader);

  emit MaterialDataChanged(shader);
  return true;

}

unsigned MaterialManager::GetNumberOfShaders() const
{
  return Shaders().size();
}

Shader* MaterialManager::GetShader(unsigned idx)
{
  return static_cast<Shader*>(Shaders()[idx]);
}

const Shader* MaterialManager::GetShader(unsigned idx) const
{
  return static_cast<const Shader*>(Shaders()[idx]);
}

void MaterialManager::RemoveShader(unsigned idx)
{
  Shader* shader = GetShader(idx);
  emit MaterialDataRemoved(shader);
  Shaders().removeAll(shader);
}

Shader* MaterialManager::CreateShader()
{
  iShader* shader = Session::Get()->GetDevice()->CreateShader(ST_Vertex);
  return new Shader (this, shader);
}

/*
 * Program handling
 */

QList<MaterialData*>& MaterialManager::Programs()
{
  return _datas["Programs"];
}


const QList<MaterialData*>& MaterialManager::Programs() const
{
  QMap<QString, QList<MaterialData*> >::const_iterator it;
  it = _datas.find("Programs");
  assert (it != _datas.end());
  return it.value ();
}




bool MaterialManager::ContainsProgram(Program *program)
{
  return Programs().contains(program);
}

bool MaterialManager::AddProgram(Program *program)
{
  if (ContainsProgram(program))
    {
      return false;
    }
  Programs().push_back(program);
  emit ProgramAdded(program);
  return true;
}

bool MaterialManager::RemoveProgram(Program *program)
{
  int idx = Programs().indexOf(program);
  if (idx < 0)
    {
      return false;
    }

  RemoveProgram(idx);
  return true;
}

void MaterialManager::MoveProgram(Program *program)
{
  assert (false);
}


bool MaterialManager::SaveProgram(Program *program)
{
  int idx = Programs().indexOf(program);
  program->Relink(0);
  if (idx < 0)
    {
      return AddProgram(program);
    }

  emit MaterialDataChanged(program);
  return true;

}

unsigned MaterialManager::GetNumberOfPrograms() const
{
  return Programs().size();
}

Program* MaterialManager::GetProgram(unsigned idx)
{
  return static_cast<Program*>(Programs()[idx]);
}

const Program* MaterialManager::GetProgram(unsigned idx) const
{
  return static_cast<const Program*>(Programs()[idx]);
}

void MaterialManager::RemoveProgram(unsigned idx)
{
  Program* program = GetProgram(idx);
  emit MaterialDataRemoved(program);
  Programs().removeAll(program);
}

Program* MaterialManager::CreateProgram()
{
  return new Program (this);
}

/*
 * Effect handling
 */

QList<MaterialData*>& MaterialManager::Effects()
{
  return _datas["Effects"];
}


const QList<MaterialData*>& MaterialManager::Effects() const
{
  QMap<QString, QList<MaterialData*> >::const_iterator it;
  it = _datas.find("Effects");
  assert (it != _datas.end());
  return it.value ();
}




bool MaterialManager::ContainsEffect(Effect *effect)
{
  return Effects().contains(effect);
}

bool MaterialManager::AddEffect(Effect *effect)
{
  if (ContainsEffect(effect))
    {
      return false;
    }
  Effects().push_back(effect);
  emit EffectAdded(effect);
  return true;
}

bool MaterialManager::RemoveEffect(Effect *effect)
{
  int idx = Effects().indexOf(effect);
  if (idx < 0)
    {
      return false;
    }

  RemoveEffect(idx);
  return true;
}

void MaterialManager::MoveEffect(Effect *effect)
{
  assert (false);
}


bool MaterialManager::SaveEffect(Effect *effect)
{
  int idx = Effects().indexOf(effect);
  if (idx < 0)
    {
      return AddEffect(effect);
    }

  emit MaterialDataChanged(effect);
  return true;

}

unsigned MaterialManager::GetNumberOfEffects() const
{
  return Effects().size();
}

Effect* MaterialManager::GetEffect(unsigned idx)
{
  return static_cast<Effect*>(Effects()[idx]);
}

const Effect* MaterialManager::GetEffect(unsigned idx) const
{
  return static_cast<const Effect*>(Effects()[idx]);
}

void MaterialManager::RemoveEffect(unsigned idx)
{
  Effect* effect = GetEffect(idx);
  emit MaterialDataRemoved(effect);
  Effects().removeAll(effect);
}

Effect* MaterialManager::CreateEffect()
{
	return new Effect (this, Session::Get ()->GetDevice ()->CreateEffect ());
}

/*
 * Material handling
 */

QList<MaterialData*>& MaterialManager::Materials()
{
  return _datas["Materials"];
}


const QList<MaterialData*>& MaterialManager::Materials() const
{
  QMap<QString, QList<MaterialData*> >::const_iterator it;
  it = _datas.find("Materials");
  assert (it != _datas.end());
  return it.value ();
}



Texture* MaterialManager::FindTexture(const iTexture *texture)
{
  if (!texture)
    {
      return 0;
    }
  QList<MaterialData*>& textures = Textures();
  foreach (MaterialData* data, textures)
    {
      Texture* txt = static_cast<Texture*>(data);
      if (txt->GetTexture() == texture)
        {
          return txt;
        }
    }

  return 0;
}



Effect* MaterialManager::FindEffect(const iEffect *effect)
{
  if (!effect)
    {
      return 0;
    }
  QList<MaterialData*>& effects = Effects();
  foreach (MaterialData* data, effects)
    {
      Effect* fx = static_cast<Effect*>(data);
			if (fx->GetEffect () == effect)
			{
				return fx;
			}
    }

  return 0;
}


Material* MaterialManager::FindMaterial(const ceMaterial *material)
{
  if (!material)
    {
      return 0;
    }
  QList<MaterialData*>& materials = Materials();
  foreach (MaterialData* data, materials)
    {
      Material* mat = static_cast<Material*>(data);
      if (mat->GetMaterial() == material)
        {
          return mat;
        }
    }

  return 0;
}
bool MaterialManager::ContainsMaterial(Material *material)
{
  return Materials().contains(material);
}

bool MaterialManager::AddMaterial(Material *material, bool defaultMaterial)
{
  if (defaultMaterial)
    {
      SetDefaultMaterial(material);
    }
  if (ContainsMaterial(material))
    {
      return false;
    }
  Materials().push_back(material);

  emit MaterialAdded(material);
  return true;
}

bool MaterialManager::RemoveMaterial(Material *material)
{
  int idx = Materials().indexOf(material);
  if (idx < 0)
    {
      return false;
    }

  RemoveMaterial(idx);
  return true;
}

void MaterialManager::MoveMaterial(Material *material)
{
  assert (false);
}


bool MaterialManager::SaveMaterial(Material *material)
{
  int idx = Materials().indexOf(material);
  if (idx < 0)
    {
      return AddMaterial(material);
    }

  emit MaterialDataChanged(material);
  return true;

}

unsigned MaterialManager::GetNumberOfMaterials() const
{
  return Materials().size();
}

Material* MaterialManager::GetMaterial(unsigned idx)
{
  return static_cast<Material*>(Materials()[idx]);
}

const Material* MaterialManager::GetMaterial(unsigned idx) const
{
  return static_cast<const Material*>(Materials()[idx]);
}

void MaterialManager::RemoveMaterial(unsigned idx)
{
  Material* material = GetMaterial(idx);
  emit MaterialDataRemoved(material);
  Materials().removeAll(material);
}

void MaterialManager::SetDefaultMaterial(Material *material)
{
  ceMaterial* mat = 0;
  if (material)
    {
      mat = material->GetMaterial();
    }

  ceMaterialManager::Get()->SetDefaultMaterial(mat);
}

Material* MaterialManager::GetDefaultMaterial()
{
  Material* material = FindMaterial(ceMaterialManager::Get()->GetDefaultMaterial());
  return material;
}

Material* MaterialManager::CreateMaterial()
{
  return new Material (this, new ceMaterial());
}



MaterialData* MaterialManager::GetMaterialData(QList<MaterialData *> &datas, const QString &name)
{
  foreach (MaterialData* data, datas)
    {

      QString refName = data->GetCategory().GetFullName() + "." + data->GetName();
      if (refName == name)
        {
          return data;
        }
      refName = data->GetCategory().GetFullName() + "." + data->GetExportName();
      if (refName == name)
        {
          return data;
        }
      refName = data->GetCategory().GetFullName() + "." + data->GetFullname();
      if (refName == name)
        {
          return data;
        }
    }
  return 0;
}

const MaterialData* MaterialManager::GetMaterialData(const QList<MaterialData *> &datas, const QString &name) const
{
  foreach (MaterialData* data, datas)
    {
      QString refName = data->GetCategory().GetFullName() + "." + data->GetName();
      if (refName == name)
        {
          return data;
        }
      refName = data->GetCategory().GetFullName() + "." + data->GetExportName();
      if (refName == name)
        {
          return data;
        }
      refName = data->GetCategory().GetFullName() + "." + data->GetFullname();
      if (refName == name)
        {
          return data;
        }
    }
  return 0;
}


Texture* MaterialManager::GetTexture(const QString &name)
{
  return static_cast<Texture*>(GetMaterialData(Textures(), name));
}

const Texture* MaterialManager::GetTexture(const QString &name) const
{
  return static_cast<const Texture*>(GetMaterialData(Textures(), name));
}

Shader* MaterialManager::GetShader(const QString &name)
{
  return static_cast<Shader*>(GetMaterialData(Shaders(), name));
}

const Shader* MaterialManager::GetShader(const QString &name) const
{
  return static_cast<const Shader*>(GetMaterialData(Shaders(), name));
}

Program* MaterialManager::GetProgram(const QString &name)
{
  return static_cast<Program*>(GetMaterialData(Programs(), name));
}

const Program* MaterialManager::GetProgram(const QString &name) const
{
  return static_cast<const Program*>(GetMaterialData(Programs(), name));
}

Effect* MaterialManager::GetEffect(const QString &name)
{
  return static_cast<Effect*>(GetMaterialData(Effects(), name));
}

const Effect* MaterialManager::GetEffect(const QString &name) const
{
  return static_cast<const Effect*>(GetMaterialData(Effects(), name));
}



Material* MaterialManager::GetMaterial(const QString &name)
{
  return static_cast<Material*>(GetMaterialData(Materials(), name));
}

const Material* MaterialManager::GetMaterial(const QString &name) const
{
  return static_cast<const Material*>(GetMaterialData(Materials(), name));
}





bool MaterialManager::CreateDefaultMaterial()
{
  if (GetDefaultMaterial() != 0)
    {
      return true;
    }
  QString vsCode = QString(
        ""
        "attribute vec4 vertex;"
        "attribute vec3 normal;"
        "attribute vec2 txt;"
        ""
        "uniform mat4 matrix_projection;"
        "uniform mat4 matrix_view;"
        "uniform mat4 matrix_world;"
        "uniform vec4 light_position;"
        ""
        "varying vec3 norm_vertex;"
        "varying vec3 norm_light;"
        ""
        "void main ()"
        "{"
        "  vec3 world_pos = (matrix_world * vertex).xyz;"
        "  norm_vertex = (matrix_world * vec4(normal, 0)).xyz;"
        "  norm_light  = normalize (light_position.xyz - world_pos);"
        "  "
        "  gl_Position = matrix_projection * matrix_view * matrix_world * vertex;"
        "}"
        );

  QString fsCode = QString (
        ""
        "uniform vec4 light_diffuse;"
        "uniform vec4 material_diffuse;"
        ""
        "varying vec3 norm_vertex;"
        "varying vec3 norm_light;"
        ""
        "void main ()"
        "{"
        "  float lambert = max (0.0, dot (normalize(norm_vertex), normalize(norm_light)));"
        "  gl_FragColor = light_diffuse * material_diffuse * lambert;"
        "}"
        );

  iDevice* device = Session::Get()->GetDevice();
  iShader* vs = device->CreateShaderFromSource(ST_Vertex,   std::string ((const char*)vsCode.toAscii()));
  iShader* fs = device->CreateShaderFromSource(ST_Fragment, std::string ((const char*)fsCode.toAscii()));
  if (!vs || !fs)
    {
      CE_UNSET(vs);
      CE_UNSET(fs);
      return false;
    }

  iProgram* prog = device->CreateProgram();
  prog->AttachShader(vs);
  prog->AttachShader(fs);
  CE_UNSET(vs);
  CE_UNSET(fs);
  if (!prog->Link())
    {
      CE_UNSET(prog);
      return false;
    }

  iEffectProgram* effect = device->CreateEffectProgram();
  effect->SetProgram(RP_Diffuse, prog);
  effect->SetProgram(RP_Depth, prog);
  effect->RegisterSemantic(PPB_MatrixProjection,  "matrix_projection");
  effect->RegisterSemantic(PPB_MatrixView,        "matrix_view");
  effect->RegisterSemantic(PPB_MatrixWorld,       "matrix_world");

  effect->RegisterSemantic(PPB_Position0,         "vertex");
  effect->RegisterSemantic(PPB_Normal0,           "normal");
  effect->RegisterSemantic(PPB_TexCoord0,         "txt");

  effect->RegisterSemantic(PPB_LightPosition,     "light_position");
  effect->RegisterSemantic(PPB_LightDiffuse,      "light_diffuse");
  effect->RegisterSemantic(PPB_MaterialDiffuse,   "material_diffuse");

	iEffect* fx = device->CreateEffect ();
	fx->AddEffectProgram (effect);

  ceMaterial* mat = new ceMaterial();
  mat->SetEffect (fx);


  Material* material = new Material(this, mat);
  material->GetCategory().SetFullName("Materials.default");
  material->SetName("Default");
  AddMaterial(material, true);

  return true;
}
